package com.hliushi.learn.exception;

import com.hliushi.learn.response.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

/**
 * @ClassName GlobalExceptionHandler
 * @Description 处理系统异常
 * @Author hliushi
 * @Date 2021/7/7 8:21
 * @Version V1.0
 **/
@Slf4j
@ControllerAdvice
@ResponseBody   // 该注解是为异常处理完之后给调用方输出一个json格式的封装数据
public class GlobalExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);


    /**
     * 缺少请求参数异常
     * 参数缺失的时候, 会抛出HttpMessageNotReadableException, 我们可以拦截该异常，做一个友好处理
     *
     * @param ex HtpMessageNotReadableException
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public JsonResult<Void> handleParameterNotReadableException(MissingServletRequestParameterException ex) {

        LOGGER.error("缺少请求参数, {}", ex.getMessage());

        return new JsonResult<>("400", "缺少必要的请求参数");
    }


    /**
     * NPE空指针异常
     *
     * @param npe NullPointerException
     * @return
     */
    @ExceptionHandler(NullPointerException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public JsonResult<Void> handleTypeMismatchException(NullPointerException npe) {

        LOGGER.error("空指针异常, {}", npe.getMessage());
        return new JsonResult<>("500", "空指针异常了");
    }


    /**
     * 拦截业务异常, 返回业务异常信息
     *
     * @param
     * @return
     */
    @ExceptionHandler(BusinessErrorException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public JsonResult<Void> handleBusinessError(BusinessErrorException ex) {
        String code = ex.getCode();
        String message = ex.getMessage();

        return new JsonResult<>(code, message);
    }

    /**
     * 拦截 ArithmeticException 异常
     */
    @ExceptionHandler(ArithmeticException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public JsonResult<Void> handleArithmeticException(ArithmeticException ex) {
        String message = ex.getMessage();
        // xxxxx ERROR c.h.l.e.GlobalExceptionHandler - ArithmeticException: / by zero
        // java.lang.ArithmeticException: / by zero
        // 	at com.hliushi.learn.controller.ExceptionController.get(ExceptionController.java:57)
        log.error("ArithmeticException: {}", ex.getLocalizedMessage(), ex);
        return new JsonResult<>("", message);
    }

    /**
     * 拦截 RuntimeException 异常
     */
    @ExceptionHandler({RuntimeException.class})
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public JsonResult<Void> handleRuntimeException(RuntimeException ex) {
        String message = ex.getMessage();

        // ERROR c.h.l.e.GlobalExceptionHandler - RuntimeException: / by zero
        // java.lang.ArithmeticException: / by zero
        // 	at com.hliushi.learn.controller.ExceptionController.get(ExceptionController.java:57)
        log.error("RuntimeException: {}", ex.getLocalizedMessage(), ex);
        return new JsonResult<>("", message);
    }


    /**
     * 系统异常, 预期以外异常
     * #
     * 拦截Exception虽然可以一劳永逸, 但是不利于我们去排查或者定位问题
     * 实际项目中, 可以把拦截Exception异常写在GlobalExceptionHandler最下面
     * 如果没有找到, 最后再拦截一下Exception, 保证输出信息友好
     * <p>
     * 2021年8月26日10:42:20 经过测试
     * 在用同一个全局异常类上, 多个匹配异常处理器规则ExceptionHandler
     * 匹配顺序: 异常层级高者优先, 再清楚点, 子类异常处理器优先
     * <p>
     * 分析: ArithmeticException 是 RuntimeException的 子类
     * 当ExceptionController类中get() 方法没有使用 try/catch 或 throw
     * 这两个处理器都声明了, 异常匹配会经过 ArithmeticException处理, 而不是它的父类RuntimeEx..
     * 当把处理 ArithmeticException 方法注释掉, 才会调用get() 方法出现异常, 异常去匹配RuntimeEx
     * 注意下: RuntimeEx.. 也是有父类的, 它的父类是Exception, 但异常分为 运行时Ex和 非运行时Ex [有空再次总结]
     * 所以来说: 一个全局异常类肯定需要一个 Exception依赖, 当一些具体异常没有在全局异常中定义
     * .        但是它会向上找自己的父类, 最终会找到Exception异常, 这样所有的异常都能处理了
     * .        Exception异常在一个全局异常类, 方法定义的顺序无关, 写在最前面和最后面都是一样的,
     * .        不过一般建议写在最后面
     * .        TODO: 存在多个全局异常类, 那么执行顺序是什么样的?
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public JsonResult<Void> handleUnexpectedServer(Exception ex) {

        LOGGER.error("系统异常: ", ex);

        return new JsonResult<>("500", "系统发生异常, 请联系管理员");
    }

}
